/*
 * Copyright (c) 2009 Roman Savelyev
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "HostLink.h"
#include "AppHookModel.h"
#include "Log.h"

#ifdef __WINS__
_LIT(KCSYName, "ECUART");
#else
_LIT(KCSYName, "ECACM");
#endif /*__WINS__*/

static const TUint32 KUnitNumber = 1;
static const TUint32 KMaxModuleNameLength = 32;
static const TUint KDeviceReadySignalLine = KSignalDCD;
static const TTimeIntervalMicroSeconds32 KBreakDuration = 160000;

CListenerCallBack::CListenerCallBack(const TCallBack& aCallBack, const TCallBack& aCancel) : CActive(CActive::EPriorityStandard),
    iCallBack(aCallBack),
    iCancel(aCancel)
    {
    CActiveScheduler::Add(this);
    }

CListenerCallBack::~CListenerCallBack()
    {
    Cancel();
    }

TRequestStatus& CListenerCallBack::Status()
    {
    return iStatus;
    }

const TRequestStatus& CListenerCallBack::Status() const
    {
    return iStatus;
    }

void CListenerCallBack::SetActive()
    {
    CActive::SetActive();
    }

void CListenerCallBack::RunL()
    {
    if (KErrCancel != iStatus.Int())
        {
        iCallBack.CallBack();
        }
    }

void CListenerCallBack::DoCancel()
    {
    iCancel.CallBack();
    }
///////////////////////////////////////////////////////////////////////////////
CHostLink* CHostLink::NewL(MHostLinkObserver& aObserver)
    {
    LOG(_L("-->CHostLink::NewL"));
    CHostLink* self = new(ELeave) CHostLink(aObserver);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    LOG(_L("<--CHostLink::NewL"));
    return self;
    }

CHostLink::CHostLink(MHostLinkObserver& aObserver) :
    iObserver(aObserver),
    iLinkStatusListener(NULL),
    iHostErrorListener(NULL),
    iDeviceErrorListener(NULL),
	iSignals(ELinkNotReady)
    {}

void CHostLink::ConstructL()
    {
    LOG(_L("-->CHostLink::ConstructL"));
    StartC32();
    User::LeaveIfError(iServer.Connect());
    User::LeaveIfError(iServer.LoadCommModule(KCSYName));
#ifdef __WINS__
    TInt err = User::LoadPhysicalDevice(_L("ECDRV"));
    __ASSERT_ALWAYS(KErrAlreadyExists == err || KErrNone == err, User::Leave(err));
    err = User::LoadLogicalDevice(_L("ECOMM"));
    __ASSERT_ALWAYS(KErrAlreadyExists == err || KErrNone == err, User::Leave(err));
#endif /*__WINS__*/
    TInt ports;
    User::LeaveIfError(iServer.NumPorts(ports));
    TSerialInfo serInfo;
    TBuf<KMaxModuleNameLength> moduleName;
    for (TInt i=0; i<ports; i++)
        {
        User::LeaveIfError(iServer.GetPortInfo(i, moduleName, serInfo));
        if (0 == moduleName.MatchF(KCSYName))
            {
            break;
            }
        }
    TBuf<KMaxPortName+4> portName;
    portName.Format(_L("%S::%d"), &serInfo.iName, KUnitNumber);
    RComm port;
    TRequestStatus portStatus(KRequestPending);
    iPort.OpenWhenAvailable(portStatus, iServer, portName);
    User::WaitForRequest(portStatus);
    User::LeaveIfError(portStatus.Int());
    User::LeaveIfError(iPort.SetAccessMode(ECommExclusive));
    TCommConfig configPkg;
    User::LeaveIfError(iPort.Config(configPkg));
    configPkg().iRate = EBps115200;
    configPkg().iParity = EParityNone;
    configPkg().iDataBits = EData8;
    configPkg().iStopBits = EStop1;
    configPkg().iFifo = EFifoEnable;
    configPkg().iHandshake = 0;
    User::LeaveIfError(iPort.SetConfig(configPkg));
    User::LeaveIfError(iPort.ResetBuffers());
    TCallBack linkStatusChangeCallBack(LinkStatusCallBack, this);
    TCallBack linkStatusChangeCancel(LinkStatusCancel, this);
    iLinkStatusListener = new(ELeave) CListenerCallBack(linkStatusChangeCallBack, linkStatusChangeCancel);
    TCallBack hostErrorCallBack(HostErrorCallBack, this);
    TCallBack hostErrorCancel(HostErrorCancel, this);
    iHostErrorListener = new(ELeave) CListenerCallBack(hostErrorCallBack, hostErrorCancel);
    TCallBack deviceErrorCallBack(DeviceErrorCallBack, this);
    TCallBack deviceErrorCancel(DeviceErrorCancel, this);
    iDeviceErrorListener = new(ELeave) CListenerCallBack(deviceErrorCallBack, deviceErrorCancel);
    GetSignalsAndNotification();
    GetBreakNotification();
    iPort.SetSignals(KDeviceReadySignalLine, 0);
    LOG(_L("<--CHostLink::ConstructL"));
    }

CHostLink::~CHostLink()
    {
    LOG(_L("-->CHostLink::~CHostLink"));
    delete iLinkStatusListener;
    delete iHostErrorListener;
    delete iDeviceErrorListener;
    iPort.SetSignals(0, KDeviceReadySignalLine);
    iPort.Close();
    iServer.Close();
    LOG(_L("<--CHostLink::~CHostLink"));
    }

void CHostLink::WriteHost(const TDesC8& aBuffer, TRequestStatus& aStatus)
    {
    LOG2(_L("-->CHostLink::WriteHost: aBuffer.Length() = %d"), aBuffer.Length());
    if (ELinkReady == LinkStatus() && !DeviceErrorInProgress())
    	{
        iPort.Write(aStatus, aBuffer);
    	}
    else
    	{
    	TRequestStatus* status = &aStatus;
    	User::RequestComplete(status, KErrNotReady);
    	}
    LOG(_L("<--CHostLink::WriteHost"));
    }

void CHostLink::CancelWriteHost()
    {
    LOG(_L("-->CHostLink::CancelWriteHost"));
    iPort.WriteCancel();
    LOG(_L("<--CHostLink::CancelWriteHost"));
    }

void CHostLink::ReadHost(TDes8& aBuffer, TRequestStatus& aStatus)
    {
    LOG2(_L("-->CHostLink::ReadHost: aBuffer.MaxLength() = %d"), aBuffer.MaxLength());
    if (ELinkReady == LinkStatus() && !DeviceErrorInProgress())
    	{
        aBuffer.Zero();
        iPort.Read(aStatus, aBuffer);
    	}
    else
    	{
    	TRequestStatus* status = &aStatus;
    	User::RequestComplete(status, KErrNotReady);
    	}
    LOG(_L("<--CHostLink::ReadHost"));
    }

void CHostLink::CancelReadHost()
    {
    LOG(_L("-->CHostLink::CancelReadHost"));
    iPort.ReadCancel();
    LOG(_L("<--CHostLink::CancelReadHost"));
    }

CHostLink::TLinkStatus CHostLink::LinkStatus() const
	{
	return (CHostLink::TLinkStatus)iSignals;
	}

void CHostLink::DeviceErrorL()
	{
	LOG(_L("-->CHostLink::DeviceErrorL"));
	__ASSERT_ALWAYS(ELinkReady == LinkStatus(), User::Leave(KErrNotReady));
	__ASSERT_ALWAYS(!DeviceErrorInProgress(), User::Leave(KErrServerBusy));
	TRequestStatus& status = iDeviceErrorListener->Status();
	status = KRequestPending;
	iPort.Break(status, KBreakDuration);
	iDeviceErrorListener->SetActive();
	LOG(_L("<--CHostLink::DeviceErrorL"));
	}

TBool CHostLink::DeviceErrorInProgress() const
	{
	return iDeviceErrorListener->IsActive();
	}

void CHostLink::GetSignalsAndNotification()
	{
	LOG(_L("-->CHostLink::GetSignalsAndNotification"));
	iSignals = iPort.Signals(ELinkReady);
	TRequestStatus& status = iLinkStatusListener->Status();
	status = KRequestPending;
	iPort.NotifySignalChange(status, iSignals, ELinkReady);
	iLinkStatusListener->SetActive();
	LOG(_L("<--CHostLink::GetSignalsAndNotification"));
	}

void CHostLink::GetBreakNotification()
	{
	LOG(_L("-->CHostLink::GetBreakNotification"));
	TRequestStatus& status = iHostErrorListener->Status();
	status = KRequestPending;
	iPort.NotifyBreak(status);
	LOG(_L("<--CHostLink::GetBreakNotification"));
	}

TInt CHostLink::LinkStatusCallBack(TAny* aThis)
    {
    CHostLink* self = (CHostLink*)aThis;
    const TRequestStatus& status = self->iLinkStatusListener->Status();
    LOG2(_L("-->CHostLink::LinkStatusCallBack(%d)"), status.Int());
    if (KErrCancel != status.Int())
    	{
    	self->GetSignalsAndNotification();
    	if (KErrNone == status.Int())
    		{
    		self->iObserver.LinkStatusChanged();
    		}
    	}
    LOG(_L("<--CHostLink::LinkStatusCallBack"));
    return KErrNone;
    }

TInt CHostLink::LinkStatusCancel(TAny* aThis)
    {
    LOG(_L("-->CHostLink::LinkStatusCancel"));
    CHostLink* self = (CHostLink*)aThis;
    self->iPort.NotifySignalChangeCancel();
    LOG(_L("<--CHostLink::LinkStatusCancel"));
    return KErrNone;
    }

TInt CHostLink::HostErrorCallBack(TAny* aThis)
    {
    CHostLink* self = (CHostLink*)aThis;
    const TRequestStatus& status = self->iLinkStatusListener->Status();
    LOG2(_L("-->CHostLink::HostErrorCallBack(%d)"), status);
    if (KErrCancel != status.Int())
    	{
    	self->GetBreakNotification();
    	if (KErrNone == status.Int())
    		{
    		self->iObserver.HostError();
    		}
    	}
    LOG(_L("<--CHostLink::HostErrorCallBack"));
    return KErrNone;
    }

TInt CHostLink::HostErrorCancel(TAny* aThis)
    {
    LOG(_L("-->CHostLink::HostErrorCancel"));
    CHostLink* self = (CHostLink*)aThis;
    self->iPort.NotifyBreakCancel();
    LOG(_L("<--CHostLink::HostErrorCancel"));
    return KErrNone;
    }

TInt CHostLink::DeviceErrorCallBack(TAny* aThis)
    {
    CHostLink* self = (CHostLink*)aThis;
    const TRequestStatus& status = self->iDeviceErrorListener->Status();
    LOG2(_L("-->CHostLink::DeviceErrorCallBack(%d)"), status.Int());
    if (KErrCancel != status.Int())
    	{
    	TInt err = self->iPort.ResetBuffers();
    	self->iObserver.DeviceErrorComplete(KErrNone != err ? err : status.Int());
    	}
    LOG(_L("<--CHostLink::DeviceErrorCallBack"));
    return KErrNone;
    }

TInt CHostLink::DeviceErrorCancel(TAny* aThis)
    {
    LOG(_L("-->CHostLink::DeviceErrorCancel"));
    CHostLink* self = (CHostLink*)aThis;
    TRequestStatus& status = self->iDeviceErrorListener->Status();
    User::WaitForRequest(status);
    LOG(_L("<--CHostLink::DeviceErrorCancel"));
    return KErrNone;
    }
